﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Markup;

using alib;
using alib.Debugging;
using alib.Enumerable;
using alib.Reflection;
using alib.String;

using agree.schema;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class EmptyDisposable : IDisposable
	{
		public static readonly EmptyDisposable Instance = new EmptyDisposable();
		private EmptyDisposable() { }
		public void Dispose() { }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[ContentProperty("Sequence")]
	[UsableDuringInitialization(true)]
	public abstract class _linq_loader<T> : _monad<IEnumerable<T>>
	{
		public _linq_loader(IRtParent parent, IEnumerable<T> src)
			: base(parent, src)
		{
		}
		public _linq_loader(IRtParent parent)
			: base(parent)
		{
		}

		public IEnumerable<T> Sequence
		{
			get { return t0; }
			set { t0 = value; }
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class Where<T> : _linq_loader<T>, IEnumerable<T>
	{
		public Where(IRtParent parent, IEnumerable<T> src)
			: base(parent, src)
		{
		}
		public Where(IRtParent parent, IEnumerable<T> src, IFunctor<T, bool> pred)
			: this(parent, src)
		{
			this.Functor = pred;
		}
		public Where(IRtParent parent)
			: base(parent)
		{
		}

		public IFunctor<T, bool> Functor { get; set; }

		public IEnumerator<T> GetEnumerator()
		{
			var pred = this.Functor.Func;
			foreach (T t in this.t0)
				if (pred(t))
					yield return t;
		}

		IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class SelectMany<T, U> : _linq_loader<T>, IActiveObj<U>, IObserver<U>, IDisposable
		where U : IIdentity
	{
		public SelectMany(IRtParent parent, IEnumerable<T> src)
			: base(parent, src)
		{
		}
		public SelectMany(IRtParent parent, IEnumerable<T> src, IFunctor<T, IActiveObj<U>> project)
			: this(parent, src)
		{
			this.Functor = project;
		}
		public SelectMany(IRtParent parent)
			: base(parent)
		{
		}

		public IFunctor<T, IActiveObj<U>> Functor { get; set; }

		public IDisposable Subscribe(IObserver<U> obs)
		{
			this._out = obs;
			this._in = t0.GetEnumerator();
			((IObserver<U>)this).OnCompleted();
			return this;
		}

		IEnumerator<T> _in;
		IObserver<U> _out;
		IDisposable dcur;
		bool f_disposed;
		int c_in;
		public override int InputItemsCount { get { return c_in; } }

		protected virtual void monad_hook(IObservable<U> monad)
		{
			dcur = monad.Subscribe(this);
		}

		void IObserver<U>.OnCompleted()
		{
			if (f_disposed)
				return;

			while (_in.MoveNext())
			{
				IObservable<U> monad = Functor.Func(_in.Current);
				if (monad != null)
				{
					monad_hook(monad);
					return;
				}
			}
			_out.OnCompleted();
		}

		void IObserver<U>.OnError(Exception error)
		{
			if (f_disposed)
				return;
			_out.OnError(error);
		}

		void IObserver<U>.OnNext(U value)
		{
			c_in++;
			if (f_disposed)
				return;
			_out.OnNext(value);
		}

		void IDisposable.Dispose()
		{
			var _tmp = Interlocked.Exchange(ref dcur, null);
			if (_tmp != null)
				_tmp.Dispose();
			f_disposed = true;
		}
	};
}